package Parse;

import Test.TestSignal;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class Grammar {
    private final String grammarsPath = "resources/ll.txt";

    private String S = "程序";
    private List<String> VT;
    private List<String> VN;
    private List<List<String>> grammars;
    private Map<String, Set<String>> first;
    private List<Set<String>> firstPerGrammar;
    private boolean[] isCalcFirst;
    private Map<String, Set<String>> follow;
    private List<Set<String>> select;

    private int[][] table;

    private String line;
    private int pos = Integer.MAX_VALUE;
    private char ch;

    public Grammar() {
        VT = Arrays.asList(
                "begin", "end", "if", "then", "else", "const", "procedure", "var",
                "do", "while", "call", "read", "write", "odd", "repeat", "until",
                "=", "<", "<=", ">=", ">", "<>", "+", "-", "*", "/", ":=", ",", ";", ".",
                "(", ")", ":", "空",
                "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
                "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
                "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
        );
        VN = Arrays.asList(
                "程序", "分程序", "常量说明部分", "变量说明部分", "过程说明部分", "语句", "多条语句",
                "赋值语句", "条件语句", "当型循环语句", "过程调用语句", "读语句", "写语句", "复合语句",
                "else语句", "重复语句", "常量定义", "多个常量定义", "无符号整数", "数字", "字母", "字符",
                "变量说明结束符", "过程首部", "表达式", "表达式前缀", "表达式后缀", "项", "项后缀",
                "加法运算符", "乘法运算符", "关系运算符", "因子", "条件", "标识符", "多个标识符"
        );
        grammars = new ArrayList<>();

        first = new HashMap<>();
        isCalcFirst = new boolean[VN.size()];
        firstPerGrammar = new ArrayList<>();

        follow = new HashMap<>();
        select = new ArrayList<>();

        table = new int[VN.size()][VT.size()];
        for (int[] ints : table) {
            Arrays.fill(ints, -1);
        }
    }

    public void constructTable() {
        analysisFirst();
        analysisFollow();
        analysisSelect();

        int i = 0;

        for (Set<String> sels : select) {
            int tableX = VN.indexOf(grammars.get(i).get(0));
            for (String sel : sels) {
                if (TestSignal.debug) {
                    System.out.println("testTable");
                    System.out.println(grammars.get(i).get(0));
                    System.out.println(tableX);
                    System.out.println(sel);
                }
                int tableY = VT.indexOf(sel);
                // 用序号来表示使用的文法
                if (TestSignal.debug) {
                    System.out.println("冲突检测");
                    if (table[tableX][tableY] != -1) {
                        System.out.println("====================");
                        System.out.println(grammars.get(i).get(0));
                        System.out.println(sel);
                        System.out.println(i);
                        System.out.println(table[tableX][tableY]);
                        System.out.println("存在冲突");
                        System.out.println("=====================");
                    }
                }
                table[tableX][tableY] = i;
            }

            i++;
        }
    }

    public void readGrammars() throws FileNotFoundException {
        File file = new File(grammarsPath);
        try (FileInputStream fis = new FileInputStream(file);
             InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
             BufferedReader br = new BufferedReader(isr)) {
            while ((line = br.readLine()) != null) {

                int size = line.length();
                pos = 0;
                List<String> tmp = new ArrayList<>();

                while (pos < size) {
                    getChar();

                    // 跳过空格和中间多余的推导符号
                    if (ch == ' ' || ch == '_') continue;
                        // 读取括号中的中文（非终结符），转换为对应的符号
                    else if (ch == '[') {
                        StringBuilder s = new StringBuilder();
                        getChar();

                        while (ch != ']') {
                            s.append(ch);
                            getChar();
                        }

                        //tmp.add(Symbol.values()[34 + VN.indexOf(s.toString())]);
                        tmp.add(s.toString());
                    }
                    // 读取终结符
                    else {
                        StringBuilder s = new StringBuilder();
                        while (ch != ' ' && ch != '$') {
                            s.append(ch);
                            getChar();
                        }
                        if (TestSignal.debug) {
                            System.out.println();
                            System.out.println(line);
                            System.out.println(s.toString());
                        }
                        //tmp.add(Symbol.values()[VT.indexOf(s.toString())]);
                        tmp.add(s.toString());
                    }
                }

                // 把读取到的语法加入到语法表里面
                if (tmp.size() > 1) grammars.add(tmp);
            }

            // 添加字母
            for (int i = 0; i < 26; i++) {
                int finalI = i;
                grammars.add(new ArrayList<>() {{
                    add("字母");
                    add(Character.toString('a' + finalI));
                }});
            }
            for (int i = 0; i < 26; i++) {
                int finalI = i;
                grammars.add(new ArrayList<>() {{
                    add("字母");
                    add(Character.toString('A' + finalI));
                }});
            }

            // 添加数字集
            for (int i = 0; i < 10; i++) {
                int finalI = i;
                grammars.add(new ArrayList<>() {{
                    add("数字");
                    add(Character.toString('0' + finalI));
                }});
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void getChar() {
        if (pos < line.length()) {
            ch = line.charAt(pos);
        } else {
            ch = '$';
        }
        pos++;
    }

    public void show() {
        for (List<String> strings : grammars) {
            System.out.println(strings);
        }
    }

    public void analysisFirst() {
        /*
            思路： 对每条语法计算first集
            如果第一个是终结符，直接把第一个加入到first集
            否则，计算非终结符的first集，然后将这个first集加入到自己的first中
         */
        for (List<String> grammar : grammars) {
            int size = grammar.size();
            boolean flag = true;
            Set<String> tmp = new HashSet<>();
            for (int i = 1; i < size; i++) {
                if (grammar.get(i).equals("空")) continue;
                if (VT.contains(grammar.get(i))) {
                    addToMap(this.first, grammar.get(0), grammar.get(i), false);
                    tmp.add(grammar.get(i));
                    flag = false;
                    break;
                } else {
                    if (TestSignal.debug) {
                        System.out.println("line 156 -> ");
                        System.out.println(grammar.get(i));
                    }
                    if (!isCalcFirst[VN.indexOf(grammar.get(i))]) calcFirst(grammar.get(i));
                    Set<String> first = getFirst(grammar.get(i));
                    if (first.contains("空")) {
                        first.remove("空");
                        tmp.addAll(first);
                        addToMap(this.first, grammar.get(0), first, true);
                    } else {
                        tmp.addAll(first);
                        addToMap(this.first, grammar.get(0), first, false);
                        flag = false;
                        break;
                    }
                }
            }

            if (flag) {
                tmp.add("空");
                addToMap(this.first, grammar.get(0), "空", false);
            }

            firstPerGrammar.add(tmp);
        }
    }

    public void calcFirst(String symbol) {
        /*
            思路： 递归查找first集
         */
        for (List<String> grammar : grammars) {
            if (grammar.get(0).equals(symbol)) {
                int size = grammar.size();
                boolean flag = true;
                for (int i = 1; i < size; i++) {
                    if (grammar.get(i).equals("空")) continue;
                    if (VT.contains(grammar.get(i))) {
                        addToMap(this.first, symbol, grammar.get(i), false);
                        flag = false;
                        break;
                    } else {
                        if (!isCalcFirst[VN.indexOf(grammar.get(i))]) calcFirst(grammar.get(i));
                        Set<String> first = getFirst(grammar.get(i));
                        if (first.contains("空")) {
                            addToMap(this.first, symbol, first, true);
                        } else {
                            addToMap(this.first, symbol, first, false);
                            flag = false;
                            break;
                        }
                    }
                }
                if (flag) addToMap(this.first, symbol, "空", false);
            }
        }

        if (VN.contains(symbol)) {
            if (TestSignal.debug) {
                System.out.println(symbol);
            }
            isCalcFirst[VN.indexOf(symbol)] = true;
        }
    }

    public void analysisFollow() {
        for (String vn : VN) {
            for (List<String> grammar : grammars) {
                int size = grammar.size();
                Set<String> tmp;

                for (int i = 1; i < size - 1; i++) {
                    if (grammar.get(i).equals(vn)) {
                        tmp = getFirst(grammar.get(i + 1));
                        addToMap(follow, vn, tmp, false);
                    }
                }

                if (grammar.get(size - 1).equals(vn)) {
                    addToMap(follow, vn, "空", false);
                }
            }
        }
    }

    public void analysisSelect() {
        int i = 0;
        for (List<String> grammar : grammars) {
            if (firstPerGrammar.get(i).contains("空")) {
                int finalI = i;
                select.add(new HashSet<>(){{
                    addAll(firstPerGrammar.get(finalI));
                    remove("空");
                    addAll(follow.get(grammar.get(0)));
                }});
            } else {
                int finalI1 = i;
                select.add(new HashSet<>(){{
                    addAll(firstPerGrammar.get(finalI1));
                }});
            }
            i++;
        }
    }

    public void addToMap(Map<String, Set<String>> m, String key, String val, boolean removeNULL) {
        if (!val.equals("空") || !removeNULL) {
            if (m.containsKey(key)) {
                m.get(key).add(val);
            } else {
                m.put(key, new HashSet<>() {{
                    add(val);
                }});
            }
        }
    }

    public void addToMap(Map<String, Set<String>> m, String key, Set<String> val, boolean removeNULL) {
        if (m.containsKey(key)) {
            m.get(key).addAll(val);
        } else {
            m.put(key, new HashSet<>() {{
                addAll(val);
            }});
        }
        if (removeNULL) m.get(key).remove("空");
    }

    public Map<String, Set<String>> getFirst() {
        return first;
    }

    public Set<String> getFirst(String key) {
        if (VT.contains(key)) return new HashSet<>() {{
            add(key);
        }};
        else return first.get(key);
    }

    public Map<String, Set<String>> getFollow() {
        return follow;
    }

    public Set<String> getFollow(String key) {
        if (VT.contains(key)) return null;
        else return follow.get(key);
    }

    public List<Set<String>> getSelect() {
        return select;
    }

    public int[][] getTable() {
        return table;
    }
}
